﻿/*  
	------------------------------------------------------------------------
	 NQ Core Library
	
	 Homepage: http://www.awzhome.de/
	------------------------------------------------------------------------
	
	This Source Code Form is subject to the terms of the Mozilla Public
	License, v. 2.0. If a copy of the MPL was not distributed with this
	file, You can obtain one at http://mozilla.org/MPL/2.0/.
   
	The Original Code is code of NQ Core Library.

	The Initial Developer of the Original Code is Andreas Weizel.
	Portions created by the Initial Developer are
	Copyright (C) 2012 Andreas Weizel. All Rights Reserved.
	
	Contributor(s): (none)
	
	------------------------------------------------------------------------
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using AWZhome.NQ.Core.Serialization;

namespace AWZhome.NQ.Core
{
	/// <summary>
	/// Component loader that uses external XML component definitions
	/// to retrieve the component and service information.
	/// </summary>
	/// <remarks>
	/// This implementation loads the XML files from "components"
	/// subdirectory or the directory given through "-cdd"
	/// command-line parameter.
	/// </remarks>
	public class NQXMLDefinitionComponentLoader : INQComponentLoader
	{
		private NQComponentInfoSerializer _serializer;
		private List<INQComponentInfo> _compInfos = new List<INQComponentInfo>();
		private List<INQServiceInfo> _servInfos = new List<INQServiceInfo>();
		private string _componentPath = String.Empty;

		private string _definitionPath = String.Empty;

		/// <summary>
		/// Creates a new instance of
		/// <see cref="AWZhome.NQ.Core.NQXMLDefinitionComponentLoader">NQXMLDefinitionComponentLoader</see>.
		/// </summary>
		public NQXMLDefinitionComponentLoader()
		{
			_serializer = new NQComponentInfoSerializer();
		}

		#region INQComponentLoader Member

		public IEnumerable<INQComponentInfo> LoadComponentData()
		{
			// Get directory with component definition files
			string[] cddParam = NQServiceManager.Instance.GetCmdLineParam("cdd");
			if ((cddParam != null) && (cddParam.Length > 0))
			{
				// Use given directory
				_definitionPath = cddParam[0];
			}
			else
			{
				// Use "components" as default subdirectory
				_definitionPath = Path.Combine(Environment.CurrentDirectory, "components");
			}

			// Iterate through all XML component files and try to deserialize them
			string fileMask = "*.xml";
			string[] fileMaskCmd = NQServiceManager.Instance.GetCmdLineParam("cdfm");
			if ((fileMaskCmd != null) && (fileMaskCmd.Length > 0))
			{
				fileMask = fileMaskCmd[0];
			}

			List<INQComponentInfo> retCompInfo = new List<INQComponentInfo>();
			foreach (string xmlFile in Directory.GetFiles(_definitionPath, fileMask))
			{
				using (FileStream fileStream = new FileStream(xmlFile, FileMode.Open))
				{
					INQComponentInfo deserializedCompInfo;
					INQServiceInfo[] deserializedServInfos;
					_serializer.ReadComponentInfo(fileStream, out deserializedCompInfo, out deserializedServInfos);

					// Add component and services to internal list
					if (_compInfos != null)
					{
						_compInfos.Add(deserializedCompInfo);
					}
					if (deserializedServInfos != null)
					{
						_servInfos.AddRange(deserializedServInfos);
					}
				}
			}

			return _compInfos;
		}

		public IEnumerable<INQServiceInfo> LoadServiceData()
		{
			return _servInfos;
		}

		public bool LoadComponentAssemblies(INQComponentInfo componentInfo)
		{
			// Try to load all assemblies of the given component
			Serialization.NQComponentInfo serComponentInfo = componentInfo as Serialization.NQComponentInfo;
			if (serComponentInfo != null)
			{
				try
				{
					// Fill the Assembly references
					int startIndex = 0;
					if (String.IsNullOrEmpty(serComponentInfo.MainAssemblyFullName))
					{
						if ((serComponentInfo.PartAssembliesFullNames != null) && (serComponentInfo.PartAssembliesFullNames.Length > 0))
						{
							// No main assembly is defined, use first part assembly instead
							serComponentInfo.MainAssemblyFullName = serComponentInfo.PartAssembliesFullNames[0];
							startIndex = 1;
						}
						else
						{
							// There are no assembly definitions at all, exit
							return false;
						}
					}

					List<Assembly> partAssemblyList = new List<Assembly>();
					AssemblyName asmName = new AssemblyName(serComponentInfo.MainAssemblyFullName);
					Assembly mainAssembly = AppDomain.CurrentDomain.Load(asmName);
					serComponentInfo.MainAssembly = mainAssembly;

					if (serComponentInfo.PartAssembliesFullNames != null)
					{
						for (int i = startIndex; i < serComponentInfo.PartAssembliesFullNames.Length; i++)
						{
							asmName = new AssemblyName(serComponentInfo.PartAssembliesFullNames[i]);
							Assembly partAssembly = AppDomain.CurrentDomain.Load(asmName);
							partAssemblyList.Add(partAssembly);
						}

						Assembly[] partAssemblies = new Assembly[partAssemblyList.Count];
						partAssemblyList.CopyTo(partAssemblies);
						serComponentInfo.PartAssemblies = partAssemblies;
					}

					// Fill type objects as well
					var componentServiceInfos =
						from sInfo in _servInfos
						where (sInfo.ParentComponent == componentInfo.Name) && (sInfo is Serialization.NQServiceInfo)
						select sInfo as Serialization.NQServiceInfo;
					foreach (Serialization.NQServiceInfo item in componentServiceInfos)
					{
						item.ServiceType = this.FindTypeInComponent(serComponentInfo, item.ServiceTypeName);
						item.ServiceInterface = this.FindServiceInterfaceInType(item.ServiceType, item.ServiceInterfaceName);

						// Get type values for the substitutes
						if (item.SubstituteNames != null)
						{
							item.Substitutes =
								(from s in item.SubstituteNames
								 select Type.GetType(s, false)).ToArray<Type>();
						}

						// Get type values for the auto-injection definitions
						if (item.AutoInjections != null)
						{
							foreach (var autoInjection in item.AutoInjections)
							{
								if (autoInjection is Serialization.NQAutoInjection)
								{
									((Serialization.NQAutoInjection) autoInjection).Interface =
										Type.GetType(((Serialization.NQAutoInjection) autoInjection).ServiceInterfaceName);
								}
							}
						}

						// Get MethodInfos for invoke and quit methods
						if (item.InvokeMethodName != null)
						{
							try
							{
								item.InvokeMethod = item.ServiceType.GetMethod(item.InvokeMethodName, new Type[] { });
							}
							finally
							{
							}
						}
						if (item.QuitMethodName != null)
						{
							try
							{
								item.QuitMethod = item.ServiceType.GetMethod(item.QuitMethodName, new Type[] { });
							}
							finally
							{
							}
						}
					}

					return true;
				}
				catch (Exception)
				{
					// Component could not be loaded completely
					return false;
				}
			}
			else
			{
				// We need componentInfo to be an AWZhome.NQ.
				return false;
			}
		}

		public string GetComponentPath()
		{
			return _componentPath;
		}

		#endregion

		#region Private members

		private Type FindTypeInComponent(INQComponentInfo compInfo, string typeName)
		{
			List<Assembly> allAssemblies = new List<Assembly>();
			allAssemblies.Add(compInfo.MainAssembly);
			if ((compInfo.PartAssemblies != null) && (compInfo.PartAssemblies.Length > 0))
			{
				allAssemblies.AddRange(compInfo.PartAssemblies);
			}

			// Search for the type in all modules of all assemblies belonging to the passed component
			foreach (Assembly assembly in allAssemblies)
			{
				foreach (Module module in assembly.GetModules(false))
				{
					Type foundType = module.GetType(typeName, false, false);
					if (foundType != null)
					{
						// We have found the type
						return foundType;
					}
				}
			}

			return null;
		}

		private Type FindServiceInterfaceInType(Type serviceType, string serviceInterfaceName)
		{
			Type resultType = serviceType;

			if (serviceType != null)
			{
				// Iterate through all interfaces implemented by the service type
				foreach (Type interfaceType in serviceType.GetInterfaces())
				{
					if (interfaceType.FullName == serviceInterfaceName)
					{
						// Found it!
						resultType = interfaceType;
						break;
					}
				}
			}

			return resultType;
		}

		#endregion

	}
}
